CSS - module - 30 - container queries

revision:


Content

CSS container queries How to use container queries ( e.g. container size queries) Container style queries Container query length units @container CSS-at-rule Example: responsive card component Gotchas Container scroll-state queries - the future Descriptors Container query syntax examples Examples Container queries based on ch Browser support Good to know...


CSS container queries

top

Why CSS container queries?

- allow to apply a style to an item depending on the size of the item's container.

By using container queries, the style of child elements within a parent element can be dynamically adjusted according to their size.

- an alternative to media queries, which apply styles to elements based on viewport size or other device characteristics.

They can only access and leverage information from the viewport, which means they can only work on a macro view of a page layout.

- a more precise tool that can support any number of layouts or layouts within layouts.

They allow to write styles that apply to the children of a container element, when that container matches certain media conditions, typically a width measurement.

If, for example, a container has less space available in the surrounding context, you can hide certain elements or use smaller fonts.

Two main types of CSS container queries:

1 - size container queries

These apply styles based on the dimensions (width/height) of a container element — similar to media queries, but scoped to a specific ancestor instead of the viewport.

Use case: responsive components that adapt to their container’s size (e.g., a card layout changing from 1 column to 2 columns when its parent is wide enough).
How to enable: set "container-type: size" or "container-type: inline-size".

inline-size: only queries the inline dimension (usually width in horizontal layouts).
size: queries both block and inline dimensions (less commonly used due to layout complexity).

2 - style container queries

These apply styles based on the computed value of a CSS custom property (--*) on the container.

Use case: theming, variant styling (e.g., "outlined" vs. "filled" buttons), or state-based design.
How to enable: set "container-type: style" or use the shorthand "container: <name> / style".

Important: style queries only work with CSS custom properties, not standard properties like "color" or "font-family".


How to use container queries ( e.g. container size queries)

top

step 1: define a container: use the "container-type" property on the parent element.

- a container context for the parent element must first be defined with the "container-type" property.

- a containment context on an element has to be declared so that the browser knows you might want to query the dimensions of this container later.

- possible values for the "container-type" property:

size : the query will be based on the "inline" and "block" dimensions of the container; applies layout, style, and size containment to the container.

inline-size : the query will be based on the "inline" dimensions of the container; applies layout, style, and inline-size containment to the element.

normal : the element is not a query container for any container size queries, but remains a query container for container style queries.

- example

        .card-container {
            container-type: inline-size; /* responds to width */
            /* or container-type: size; for both width and height */
        }
    

note: the container must have a computed display value that establishes a layout containment (e.g., block, flex, grid). Avoid “display: contents”.

step 2: write a containment query: use @container (or @container <name>) to apply styles based on the container’s size

- example

        @container card (min-width: 300px) {
          .card-title {
            font-size: 1.5rem;
          }
        }

        /* Or without a name (targets nearest ancestor with container-type) */
        @container (max-width: 299px) {
          .card-title {
            font-size: 1rem;
          }
        }
    

Container style queries

top

- are a feature in CSS that allow you to apply styles to elements based on the computed styles of their container, rather than just the viewport (as with traditional media queries) or the presence of features (as with feature queries).
- are part of the broader CSS Container Queries specification, which also includes container size queries.

key concepts

1: container establishment

To use style queries (or any container query), you first need to declare an element as a container using the container-type or container shorthand property.

example

      .card {
          container-type: inline-size; /* for size queries */
          /* OR */
          container: card / style;     /* named container with style type */
      }
    

For style queries, you must explicitly enable style containment.

example

      .theme-container {
          container: theme / style;
      }
    

2: style queries syntax

You can query specific computed style values of the container using the @container rule with a style condition.

example

      @container style(--theme: dark) {
          h2 {
            color: white;
          }
      }
      

explanation: this applies styles to "h2" elements inside any container where the custom property "--theme" is set to dark.

3: custom properties only

As of current specifications (early 2026), style queries can only observe CSS custom properties (--*), not arbitrary computed styles like "color" or "font-siz"e. This is for performance and predictability reasons.

4: named vs. anonymous containers

It’s often useful to name your containers to avoid ambiguity.

example

      .card {
          container: card-style / style;
      }

      @container card-style style(--variant: outlined) {
          border: 2px solid;
      }
    

5: browser support (as of ealy 2026)

Chrome/Edge: supported since ~v110.
Firefox: supported since v117.
Safari: supported since v16.4

example

Dark Theme Title

code:
            <div class="theme-container" style="--theme: dark">
                <article>
                  <h2>Dark Theme Title</h2>
                </article>
            </div>
            <style>
              .theme-container { container: my-theme / style;}
              @container my-theme style(--theme: dark) {
                  h2 {
                      color: #fff;
                      background: #000;
                    }
                  }
              @container my-theme style(--theme: light) {
                  h2 {
                    color: #000;
                    background: #fff;
                  }
              }
            </style>
        

gotchas

Style queries do not work without container-type: style (or container: / style).
Avoid querying non-custom properties — they won’t work.
Combine with size-based container queries for highly adaptive components.


Container query length units

top

These specify a length relative to the dimensions of a query container.
Components that use units of length relative to their container are more flexible to use in different containers without having to recalculate concrete length values.

The container query length units are:

cqw : 1% of a query container's width.
cqh : 1% of a query container's height.
cqi : 1% of a query container's inline size.
cqb: 1% of a query container's block size.
cqmin: the smaller value of either cqi or cqb.
cqmax: the larger value of either cqi or cqb.

Fallbacks for container queries

For browsers that don't yet support container queries, grid and flex can be used to create a similar effect.


@container CSS-at-rule

top

This at-rule is a conditional group rule that applies styles to a containment context.
Style declarations are filtered by a condition and applied to the container if the condition is true. The condition is evaluated when the queried container size, <style-feature>, or scroll-state changes.

The "container-name" property specifies a list of query container names.
These names can be used by @container rules to filter which query containers are targeted.
The optional, case-sensitive <container-name> filters the query containers that are targeted by the query.

Once an eligible query container has been selected for an element, each container feature in the <container-condition> is evaluated against that query container.

Syntax @container # { <stylesheet>}

Values:

<container-condition> : an optional <container-name> and a <container-query>. Styles defined in the <stylesheet> are applied if the condition is true.

<container-name> : optional. The name of the container that the styles will be applied to when the query evaluates to true, specified as an <ident>.
<container-query> : a set of features that are evaluated against the query container when the size, <style-feature>, or scroll-state of the container changes.

<stylesheet> : a set of CSS rules or declarations.

Examples

      /* With a <size-query> */
        @container (width > 400px) {
          h2 { font-size: 1.5em;}
        }
        
        /* With an optional <container-name> */
        @container tall (height > 30rem) {
          p {line-height: 1.6;}
        }
        
        /* With a <scroll-state> */
        @container scroll-state(scrollable: top) {
          .back-to-top-link {visibility: visible;}
        }
        
        /* With a <container-name> and a <scroll-state> */
        @container sticky-heading scroll-state(stuck: top) {
          h2 {background: purple;color: white;}
        }
        
        /* Multiple queries in a single condition */
        @container (width > 400px) and style(--responsive: true) {
          h2 {font-size: 1.5em;}
        }
        
        /* Condition list */
        @container card (width > 400px), style(--responsive: true), scroll-state(stuck: top) {
          h2 {font-size: 1.5em;}
        }
     

Example: responsive card component

top

Hello

Content here...

code:
              <div class="layout">
                <div class="card-container">
                    <div class="card">
                      <h4 class="card-title">Hello</4>
                      <p>Content here...</p>
                    </div>
                </div>
              </div>
              <style>
                .layout { display: grid; grid-template-columns: repeat(auto-fit, minmax(20vw, 1fr)); gap: 1rem;margin-inline: 2vw;}
                .card-container { container: card / inline-size;}
                .card { border: 0.1vw solid #ccc; padding: 1rem;}
                .card-title {font-size: 1rem; }
                @container card (min-width: 25vw) {
                  .card-title { font-size: 1.8rem; color: navy;}
                }
              </style>
        

Gotchas

top

1/ Naming containment contexts

It's also possible to give a containment context a name using the "container-name" property. Once named, the name can be used in a "@container" query so as to target a specific container.

examples

            <div class="post">
                <div class="card">
                    <h4>Card title</h4>
                    <p>Card content</p>
                </div>
            </div>
            <style>
                .post {container-type: inline-size; container-name: sidebar;}
                @container sidebar (min-width: 700px) {
                .card {font-size: 2em;}
                }
            </style>
        

2/ Shorthand container syntax

The shorthand way of declaring a containment context is to use the "container" property:

.post{ container: sidebar / inline-size;}

The "shorthand syntax" for named containment contexts is to use container in the form container: <name> / <type>.

Examples

      .post {container-name: sidebar; container-type: inline-size;}
      .post {container: sidebar / inline-size; }
      @container sidebar (width > 400px) { /* <stylesheet> */}
    

3/ Logical keywords in container queries

Logical keywords can be used to define the container condition:

and combines two or more conditions.
or combines two or more conditions.
not negates the condition. Only one 'not' condition is allowed per container query and cannot be used with the "and" or "or" keywords.

Examples

          @container (width > 400px) and (height > 400px) {
            /* <stylesheet> */
          }
          
          @container (width > 400px) or (height > 400px) {
            /* <stylesheet> */
          }
          
          @container not (width < 400px) {
            /* <stylesheet> */
          }
    

Container scroll-state queries - the future

top

As of early 2026, container scroll-state queries are not yet part of the official "CSS Container Queries specification" and are not supported in any major browser.

Scroll-based container queries (e.g., @container (scroll-block: auto)) have been discussed in W3C working groups and are considered a potential future extension, but they remain experimental and non-standardized.


Descriptors

top

1 - Size container descriptors

The <container-condition> can include one or more boolean size queries, each within a set of parentheses.
A size query includes a size descriptor, a value, and — depending on the descriptor — a comparison operator.
The syntax for including multiple conditions is the same as for "@media" size feature queries.

Descriptors:

aspect-ratio : the aspect-ratio of the container calculated as the width to the height of the container expressed as a value.
block-size : the block-size of the container expressed as a <length> value.
height : the height of the container expressed as a <length> value.
inline-size : the inline-size of the container expressed as a <length> value.
orientation : the orientation of the container, either landscape or portrait.
width : the width of the container expressed as a <length> value.

Example

          @container (min-width: 400px) { /* … */ }

          @container (orientation: landscape) and (width > 400px) {   /* … */  }

          @container (15em <= block-size <= 30em) { /* … */}
    

Scroll-state container descriptors

These are specified inside the <container-condition> within a set of parentheses following the "scroll-state" keyword.

Example

          @container scroll-state(scrollable: top) {
            /* … */
          }
          @container scroll-state(stuck: inline-end) {
            /* … */
          }
          @container scroll-state(snapped: both) {
            /* … */
          }
    

Supported keywords for scroll-state container descriptors include physical and flow relative values:

scrollable : queries whether the container can be scrolled in the given direction via user-initiated scrolling, such as by dragging the scrollbar or using a trackpad gesture. In other words, is there overflowing content in the given direction that can be scrolled to? Valid scrollable values include the following keywords:

none : the container is not a scroll container or otherwise cannot be scrolled in any direction.
top : the container can be scrolled towards its top edge.
right : the container can be scrolled towards its right-hand edge.
bottom : the container can be scrolled towards its bottom edge.
left : the container can be scrolled towards its left-hand edge.
x : the container can be scrolled horizontally towards either or both of its left-hand or right-hand edges.
y : the container can be scrolled vertically towards either or both of its top or bottom edges.
block-start : the container can be scrolled towards its block-start edge.
block-end : the container can be scrolled towards its block-end edge.
inline-start : the container can be scrolled towards its inline-start edge.
inline-end : the container can be scrolled towards its inline-end edge.
block : the container can be scrolled in its block direction towards either or both of its block-start or block-end edges.
inline : the container can be scrolled in its inline direction towards either or both of its inline-start and inline-end edges.
P. S. If the test passes, the rules inside the @container block are applied to descendants of the scroll container.

If the test passes, the rules inside the @container block are applied to descendants of the scroll container.

To evaluate whether a container is scrollable, without being concerned about the direction, use the "none" value with the "not" operator.

          @container not scroll-state(scrollable: none) {
            /* … */
          }
    

snapped : queries whether the container is, or will be, snapped to a scroll snap container ancestor along the given axis. Valid snapped values include the following keywords:

none : the container is not a scroll snap target for its ancestor scroll container. When implementing a "snapped: none" query, containers that are snap targets for the scroll container will not have the @container styles applied, whereas non-snap targets will have the styles applied.
x : the container is a horizontal scroll snap target for its ancestor scroll container, that is, it is snapping horizontally to its ancestor.
y : the container is a vertical scroll snap target for its ancestor scroll container, that is, it is snapping vertically to its ancestor.
block : the container is a block-axis scroll snap target for its ancestor scroll container, that is, it is snapping to its ancestor in the block direction.
inline : the container is an inline-axis scroll snap target for its ancestor scroll container, that is, it is snapping to its ancestor in the inline direction.
both : the container is both a horizontal and vertical scroll snap target for its ancestor scroll container and is snapping to its ancestor in both directions. The container won't match if it is only snapping to its ancestor along the horizontal or vertical axis. It needs to be both.

To evaluate a container with a non- "none snapped" scroll-state query, it must be a container with a scroll container ancestor having a "scroll-snap-type" value other than "none". A "snapped: none" query will match even when there is no scroll container ancestor.
Evaluations occur when "scrollsnapchanging" events fire on the scroll snap container. If the test passes, the rules inside the "@container" block are applied to descendants of the container.

To evaluate whether a container is a snap target, without being concerned about the direction, use the "none" value with the "not" operator

 
          @container not scroll-state(snapped: none) {
            /* … */
          }
    

stuck : queries whether a container with a position value of "sticky" is stuck to an edge of its scrolling container ancestor. Valid stuck values include the following keywords:

none : the container is not stuck to any edges of its container. Note that "none" queries will match even if the container does not have "position: sticky" set on it.
top : the container is stuck to the top edge of its container.
right : the container is stuck to the right-hand edge of its container.
bottom : the container is stuck to the bottom edge of its container.
left : the container is stuck to the left-hand edge of its container.
block-start : the container is stuck to the block-start edge of its container.
block-end : the container is stuck to the block-end edge of its container.
inline-start : the container is stuck to the inline-start edge of its container.
inline-end : the container is stuck to the inline-end edge of its container.

To evaluate a container with a non-"none stuck" scroll-state query, it must have "position: sticky" set on it, and be inside a scroll container. If the test passes, the rules inside the "@container block" are applied to descendants of the "position: sticky" container.

It is possible for two values from opposite axes to match at the same time:

 
          @container scroll-state((stuck: top) and (stuck: left)) {
            /* … */
          }
    

However, two values from opposite edges will never match at the same time:

            @container scroll-state((stuck: left) and (stuck: right)) {
          /* … */
        }
      

To evaluate whether a container is stuck, without being concerned about the direction, use the none value with the not operator:

         @container not scroll-state(stuck: none) {
        /* … */
      }
    


Container query syntax examples

top

container query syntax examples

            
                .parent {container-name: hero-banner; container-type: inline-size;  
                  /* or container: hero-banner / inline-size; */
                }
                .child {display: flex; flex-direction: column;}
              
              /* When the container is greater than 60 characters... */
              @container hero-banner (width > 60ch) {
                  /* Change the flex direction of the .child element. */
                  .child { flex-direction: row;}
              }

              header {container-name: headerContainer; container-type: inline-size;}
              @container (width >= 420px) {
                h1 {font-size: calc(1.6em + 2vw);}
              }
              @container (width >= 920px) {
                h1 {font-size: calc(2.1em + 2vw);}
              }

              .grid-parent { container-type: inline-size; }
              .grid {display: grid;gap: 1rem; 
                 @container (width > 90ch) { grid-template-columns: repeat(3, 1fr);}
              }

            

registering elements as containers

            .cards {container-name: card-grid; container-type: inline-size;
              /* Shorthand */ container: card-grid / inline-size;
            }
            .card-container {container-type: size | inline-size | normal;}
            .card-container {container-type: inline-size; container-name: cardContainer;}

            .card-container {container: cardContainer / inline-size;}
          

querying a container

            @container my-container (width > 60ch) {
              article {flex-direction: row;}
            }

            @container cardContainer (max-width: 500px) {
              .card {flex-direction: column;}
            }
            
            @container (max-width: 500px) {/* Normal css styles */}
            @container (min-width: 500px) {/* Normal css styles */}
            @container (width >= 500px) {/* Normal css styles */}
            @container (width < 500px) {/* Normal css styles */}
            @container (width <= 500px) {/* Normal css styles */}

            @container (width >= 500px) and (height >= 500px) {/* Normal css styles */}
            @container (width > 760px) not (height > 670px) {/* Normal css styles */}
            @container not (height <= 1080px) {/* Normal css styles */}
            @container (width < 500px) or (height < 500px) {/* Normal css styles */}
          

properties and values

            container-name: none | +;
            container-type: normal | size | inline-size;
            container: <'container-name'> [ / <'container-type'> ]?
          

style containers

            article {container-name: card;}
            @container style(--bg-color: #000) {
              p { color: #fff; }
            }
          

custom properties and variables

            .card-wrapper {--bg-color: #000;}
            .card { @container style(--bg-color: #000) { /* Custom CSS */ } }
          

nesting style queries

            @container style(--featured: true) {
                article { grid-column: 1 / -1; }
                @container style(--theme: dark) {
                  article { --bg-color: #000; --text: #fff;}
                }
            }
          

Examples

top

card container

Shanghai trip!

June 1, 2023

There is a lot to say and write about this metropole, but words are arguably not enough to describe and depict the amazing facts in this city.

code:
                  <div class="card-container">
                      <div class="card-child">
                        <div class="image">
                          <img src="../../pics/2018-Sh-04.jpg" alt="">
                        </div>
                        <div class="meta">
                          <h4>Shanghai trip!</h4>
                          <i>June 1, 2023</i>
                          <p>There is a lot to say and write about this metropole, but words are arguably not enough to 
                          describe and depict the amazing facts in this city.</p>
                        </div>
                      </div>
                  </div>
                  <style>
                      .card-container {container: card / inline-size;}
                      .card-child {display: grid; grid-template-columns: 1fr 1fr; }
                      .card-child h4 {font-size: clamp(1vw, 7cqi, 4vw);}
                      @container (max-width: 45vw) {
                          .card-child {grid-template-columns: 1fr;}
                          .meta {padding: 1vw;}
                          img {aspect-ratio: 16 / 9;}
                      }
          
                      @layer base {
                          .card-container {background: #ffe4e8; overflow: hidden; resize: horizontal; width: 25vw; max-width: 40vw; 
                              min-width: 15vw;}
                          .meta {padding: 2vw;}
                          img {width: 100%; height: 100%; aspect-ratio: 1/1; object-fit: cover; object-position: 20% 20%;}
                      }
                  </style>
              

card

Card title

Card content

            <div class="post">
                <div class="card">
                  <h4>Card title</h4>
                  <p>Card content</p>
                </div>
            </div>
            <style>
                .post {container-type: size;}
                .card h4{font-size: 1em; color: blue; }
                @container (min-width: 700px) {
                    .card h4 {font-size: 3em;color: purple;}
                }
            </style>
            

container query calendar

top
  1. 9 a.m.
  2. 10 a.m.
  3. 11 a.m.
  4. 12 p.m.
  5. 1 p.m.
  6. 2 p.m.
  7. 3 p.m.
  8. 4 p.m.
  9. 5 p.m.
5
Mon
Planning
Team Lunch
6
Tue
Work Out
Doctor
Soccer
7
Wed
Customer Call
Team Event
8
Thu
Work Out
9
Fri
No Meetings

Container-Responsive Calendar Component

Play with the bar in the middle to resize things and see that it's not media queries on the viewport changing the layout and style of the calendar, it is container queries.

code:
      <split-panel direction="row" class="grid">
        <div slot="1">
          <div class="calendar-wrap">
            <div class="calendar">
              <div class="hours">
                <ol>
                  <li><div class="day-time">9 a.m.</div></li>
                  <li><div class="day-time">10 a.m.</div></li>
                  <li><div class="day-time">11 a.m.</div></li>
                  <li><div class="day-time">12 p.m.</div></li>
                  <li><div class="day-time">1 p.m.</div></li>
                  <li><div class="day-time">2 p.m.</div></li>
                  <li><div class="day-time">3 p.m.</div></li>
                  <li><div class="day-time">4 p.m.</div></li>
                  <li><div class="day-time">5 p.m.</div> </li>
                </ol>
              </div>
              <dl class="events">
                <div class="day-of-events" data-day="Monday">
                  <dt>
                    <div class="day-number">5</div>
                    <div class="day-name">Mon</div>
                  </dt>
                  <dd class="event" style="--row: 2; --span: 2;">
                    <time> 9 - 11 a.m.</time>
                    <div class="event-title">
                      Planning
                    </div>
                  </dd>
                  <dd class="event" style="--row: 5; --span: 2;">
                    <time>12 - 2 p.m.</time>
                    <div class="event-title">
                      Team Lunch
                    </div>
                  </dd>
                </div>
                <div class="day-of-events" data-day="Tuesday">
                  <dt>
                    <div class="day-number">6</div>
                    <div class="day-name">Tue</div>
                  </dt>
                  <dd class="event" style="--row: 5; --span: 1;">
                    <time>12 - 1 p.m.</time>
                    <div class="event-title">
                      Work Out
                    </div>
                  </dd>
                  <dd class="event" style="--row: 7; --span: 1;">
                    <time>2 - 3 p.m.</time>
                    <div class="event-title">
                      Doctor
                    </div>
                  </dd>
                  <dd class="event" style="--row: 9; --span: 2;">
                    <time>4 - 6 p.m.</time>
                    <div class="event-title">
                      Soccer
                    </div>
                  </dd>
                </div>
                <div class="day-of-events" data-day="Wednesday">
                  <dt>
                    <div class="day-number">7</div>
                    <div class="day-name">Wed</div>
                  </dt>
      
                  <dd class="event" style="--row: 2; --span: 2;">
                    <time> 9 - 11 a.m.</time>
                    <div class="event-title">
                      Customer Call
                    </div>
                  </dd>
                  <dd class="event" style="--row: 7; --span: 4;">
                    <time>2 - 6 p.m.</time>
                    <div class="event-title">
                      Team Event
                    </div>
                  </dd>
                </div>
                <div class="day-of-events" data-day="Thursday">
                  <dt>
                    <div class="day-number">8</div>
                    <div class="day-name">Thu</div>
                  </dt>
      
                  <dd class="event" style="--row: 5; --span: 1;">
                    <time>12 - 1 p.m.</time>
                    <div class="event-title">
                      Work Out
                    </div>
                  </dd>
                </div>
                <div class="day-of-events" data-day="Friday">
                  <dt>
                    <div class="day-number">9</div>
                    <div class="day-name">Fri</div>
                  </dt>
                  <dd class="event" style="--row: 2; --span: 9;">
                    <time>9 - 6 p.m.</time>
                    <div class="event-title">
                      No Meetings
                    </div>
                  </dd>
                </div>
              </dl>
            </div>
          </div>
        </div>
        </div><br>
        <div slot="2">
          <div class="info-panel">
            <h4>Container-Responsive Calendar Component</h4>
            <p>Play with the bar in the middle to resize things and see that it's not media queries on the viewport changing the layout and style of the calendar, it is container queries.</p>
          </div>
        </div>
        </split-panel>
        <style>
            .calendar-wrap {container: Calendar / inline-size;background-color: skyblue;}
            .calendar {--dayHeaderHeight: 75px; --hourHeight: 50px;display: flex;
              .hours {
                > ol > li:first-of-type {margin-block-start: var(--dayHeaderHeight);}
                > ol > li {block-size: var(--hourHeight);}
                white-space: nowrap;
                border-inline-end: 1px solid var(--gray);
                .day-time {padding-block: 0.25rem; padding-inline-end: 0.5rem; border-block-start: 1px solid var(--gray);}
              }
              .days { width: 100%; }
              .day-number {font-weight: 300; font-size: 2rem;}
              .day-name {text-transform: uppercase; font-weight: 500;}
              .events {display: flex; width: 100%;
                .day-of-events {flex: 1; padding-inline: 0.75rem; display: grid; grid-template-rows: var(--dayHeaderHeight) repeat(9, var(--hourHeight)); grid-template-columns: 1fr; border-inline-end: 1px solid var(--gray);
                  & dt { grid-row: 1; }
                }
                .event {--gray: #555; grid-row: var(--row) / span var(--span, 1);  border: 1px solid var(--gray); border-radius: 3px;           padding: 0.5rem;}
                  & time {font-weight: 600; margin-block-end: 0.15rem;}
                .event-title {opacity: 0.6;}
              }

              @container Calendar (max-width: 690px) {
                & {display: block;}
                .hours {display: none;}
                .events {display: block;
                  .day-of-events {border: 0; grid-template-columns: 75px 1fr; grid-template-rows: auto; gap: 0.75rem; margin-block-end: 2rem;
                    .event {grid-column: 2; grid-row: auto; }
                  }
                }
              }
              @container Calendar (max-width: 350px) {
                .events {
                  .day-of-events {display: block;
                    .event { margin-block-end: 0.25rem; }
                    & dt {display: flex; margin-block-end: 0.25rem;
                      .day-number {font-size: 1.2rem; font-weight: 800; margin-inline-end: 0.25rem;}
                      .day-name {font-size: 1.2rem;}
                    }
                  }
                }
              }
            }
            .info-panel { padding: 1rem 2rem; container: InfoPanel / inline-size;
              & h1 {font-size: max(7cqi, 24px); }
            }
            split-panel { background: white; padding: 1rem 0.5rem; }
      </style>
      <script>
                    
        function fireEvent(
          element,
          eventName,
          data,
          bubbles = true,
          cancelable = true
        ) {
          const event = document.createEvent("HTMLEvents");
          event.initEvent(eventName, bubbles, cancelable); // event type,bubbling,cancelable
          event.data = data;
          return element.dispatchEvent(event);
        }

        class WcSplitPanel extends HTMLElement {
          static observedAttributes = ["direction"];
          #direction = "row";
          #isResizing = false;
          constructor() {
            super();
            this.bind(this);
          }
          bind(element) {
            element.attachEvents = element.attachEvents.bind(element);
            element.render = element.render.bind(element);
            element.cacheDom = element.cacheDom.bind(element);
            element.pointerdown = element.pointerdown.bind(element);
            element.resizeDrag = element.resizeDrag.bind(element);
          }
          render() {
            this.attachShadow({ mode: "open" });
            this.shadowRoot.innerHTML = `
                    <style>
                        :host{ display: grid; }
                        :host([resizing]){ user-select: none; }
                        :host([resizing][direction=row]){ cursor: col-resize; }
                        :host([direction=row]) { grid-template-columns: var(--first-size, 1fr) max-content var(--second-size, 1fr); }
                        :host([direction=row]) #median { inline-size: 0.5rem; grid-column: 2 / 3; }
                        :host([direction=row]) #median:hover { cursor: col-resize; }
                        :host([direction=row]) #slot1 { grid-column: 1 / 2; grid-row: 1 / 1; }
                        :host([direction=row]) #slot2 { grid-column: 3 / 4; grid-row: 1 / 1; }

                        :host([resizing][direction=col]){ cursor: row-resize; }
                        :host([direction=column]) { grid-template-rows: var(--first-size, 1fr) max-content var(--second-size, 1fr); }
                        :host([direction=column]) #median { block-size: 0.5rem; grid-row: 2 / 3; }
                        :host([direction=column]) #median:hover { cursor: row-resize; }
                        :host([direction=column]) #slot1 { grid-row: 1 / 2; grid-column: 1 / 1; }
                        :host([direction=column]) #slot2 { grid-row: 3 / 4; grid-column: 1 / 1; }

                        #median { background: #ccc; }
                        ::slotted(*) { overflow: auto; }
                    </style>
                    <slot id="slot1" name="1"></slot>
                    <div id="median" part="median"></div>
                    <slot id="slot2" name="2"></slot>
                `;
          }
          connectedCallback() {
            this.render();
            this.cacheDom();
            this.attachEvents();
          }
          cacheDom() {
            this.dom = {
              median: this.shadowRoot.querySelector("#median")
            };
          }
          attachEvents() {
            this.dom.median.addEventListener("pointerdown", this.pointerdown);
          }
          pointerdown(e) {
            this.isResizing = true;
            const clientRect = this.getBoundingClientRect();
            this.left = clientRect.x;
            this.top = clientRect.y;
            this.addEventListener("pointermove", this.resizeDrag);
            this.addEventListener("pointerup", this.pointerup);
          }
          pointerup() {
            this.isResizing = false;
            fireEvent(this, "sizechanged");
            this.removeEventListener("pointermove", this.resizeDrag);
            this.removeEventListener("pointerup", this.pointerup);
          }
          resizeDrag(e) {
            if (this.direction === "row") {
              const newMedianLeft = e.clientX - this.left;
              const median = this.dom.median.getBoundingClientRect().width;
              this.style.gridTemplateColumns = `calc(${newMedianLeft}px - ${
                median / 2
              }px) ${median}px 1fr`;
            }
            if (this.direction === "column") {
              const newMedianTop = e.clientY - this.top;
              const median = this.dom.median.getBoundingClientRect().height;
              this.style.gridTemplateRows = `calc(${newMedianTop}px - ${
                median / 2
              }px) ${median}px 1fr`;
            }
          }
          attributeChangedCallback(name, oldValue, newValue) {
            if (newValue != oldValue) {
              this[name] = newValue;
            }
          }
          set isResizing(value) {
            this.#isResizing = value;
            if (value) {
              this.setAttribute("resizing", "");
            } else {
              this.style.userSelect = "";
              this.style.cursor = "";
              this.removeAttribute("resizing");
            }
          }
          get isResizing() {
            return this.#isResizing;
          }
          set direction(value) {
            this.#direction = value;
            this.setAttribute("direction", value);
            this.style.gridTemplateRows = "";
            this.style.gridTemplateColumns = "";
          }
          get direction() {
            return this.#direction;
          }
        }
        customElements.define("split-panel", WcSplitPanel);
        //Page UI
        document.addEventListener("DOMContentLoaded", () => {
          const panel = document.querySelector("split-panel");
          panel.addEventListener("sizechanged", (e) => {
            console.log("size changed!");
          });
        });
      </script>
    

style query examples

--variant: 1

Card

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem eum a nisi quo quisquam deserunt.

--variant: 2

Card

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem eum a nisi quo quisquam deserunt.

code:
        <div class="grid_A;">
          <fieldset style="--variant: 1;" class="container">
              <legend>--variant: 1</legend>
              <button>Button</button>
              <article class="card">
                <h4>Card</h4>
                <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem eum a nisi quo 
                quisquam deserunt.</p>
              </article>
          </fieldset>
          <fieldset style="--variant: 2;" class="container">
              <legend>--variant: 2</legend>
              <button>Button</button>
              <article class="card">
                <h4>Card</h4>
                <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem eum a nisi quo 
                quisquam deserunt.</p>
              </article>
          </fieldset>
        </div>
        <style>
          @container style(--variant: 1) {button,
            .card {--background: #eee; --foreground: #ccc;  color: black; border: 1px solid var(--background); 
              border-radius: 0.5rem;
              padding: 1rem; background: radial-gradient( circle at top center,  var(--background), 
              var(--foreground)); box-shadow: 0 0.25rem 0.5rem 0 rgba(0, 0, 0, 0.2);}
          }
          @container style(--variant: 2) {button,
            .card { background: #333; color: #eee;  padding: 1rem; border-radius: 3px; border: 0; 
               border-bottom: 4px solid orange; }
            .card { border-start-end-radius: 200px 20px; }
          }
          .container {display: grid; gap: 1rem; max-inline-size: 20rem; }
          h4 {letter-spacing: -0.03rem;}
          fieldset {padding: 2rem;}
          legend {padding-bottom: 1rem; font-family: monospace; font-weight: 600; }
        </style>
      

demo of container units

Resize Me

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Omnis laudantium ipsam est pariatur quae sequi facere, amet et ipsum. Hic repellendus fuga deleniti alias laudantium tempora eius animi distinctio officiis.

code:
        <div class="container1">
          <div class="card">
            <h4>Resize Me</h4>
            <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Omnis laudantium 
            ipsam est pariatur quae sequi facere, amet et ipsum. Hic repellendus fuga deleniti 
            alias laudantium tempora eius animi distinctio officiis.</p>
          </div>
        </div>
        <style>
          .container1 {container: my-thing / inline-size; background: tan; inline-size: 300px; 
            min-inline-size: 200px; resize: both; overflow: hidden;
            /* Can't use container units here! */
            .card { padding: 5cqi; font-size: 4cqi;  border: 1cqi solid brown; height: 100%; }
            h4 {font-size: 10cqi; margin-block: 0 3cqi;}
          }
        </style>
      

Container queries based on ch

top

example 1

Regular font size:

Small font size:

Large font size:

Regular font size, narrower container:

code:
        <div class="ex1">
          <section>
              <h4 class="section-title">Regular font size:</h4>
              <div class="gridd">
                  <div></div>
                  <div></div>
                  <div></div>
              </div>
          </section>
          <section class='small-font-size'>
              <h4 class="section-title">Small font size:</h4>
              <div class="gridd">
                <div></div>
                <div></div>
                <div></div>
              </div>
          </section>
          <section class='large-font-size'>
              <h4 class="section-title">Large font size:</h4>
              <div class="gridd">
                <div></div>
                <div></div>
                <div></div>
              </div>
          </section>
          <section class='narrow'>
              <h4 class="section-title">Regular font size, narrower container:</h4>
              <div class="gridd">
                <div></div>
                <div></div>
                <div></div>
              </div>
          </section>
        </div>
        <style>
          section {container-type: inline-size;}
          .small-font-size {font-size: 0.5rem;}
          .large-font-size {font-size: 1.5rem;}
          .narrow {width: 70%; margin-inline: auto;}
          .gridd { display: grid; gap: 1rem;
              @container (width > 90ch) {
                grid-template-columns: repeat(3, 1fr);
              }
            }
          @layer not-demo-related {
            .ex1{display: grid; gap: 2rem; font-family: system-ui;}
            .section-title {font-size: 1.5rem;}
            .gridd {padding: 1rem; border: 3px solid dodgerblue;}
            .gridd > * { padding: 1rem; background: orangered;}
          }
        </style>
      

example 2

Regular font size:

Small font size:

Large font size:

code:
          <div class="ex1">
            <section>
                <h4 class="section-title">Regular font size:</h4>
                <div class="gridd">
                    <div></div>
                    <div></div>
                    <div></div>
                </div>
            </section>
            <section class='small-font-size'>
                <h4 class="section-title">Small font size:</h4>
                <div class="gridd">
                  <div></div>
                  <div></div>
                  <div></div>
                </div>
            </section>
            <section class='large-font-size'>
                <h4 class="section-title">Large font size:</h4>
                <div class="gridd">
                  <div></div>
                  <div></div>
                  <div></div>
                </div>
            </section>
            
          </div>
          <style>
            section {container-type: inline-size;}
            .small-font-size {font-size: 0.5rem;}
            .large-font-size {font-size: 1.5rem;}
            .narrow {width: 70%; margin-inline: auto;}
            .gridd { display: grid; gap: 1rem;
                @container (width > calc(30ch * 3)) {
                  grid-template-columns: repeat(3, 1fr);
                }
              }
            @layer not-demo-related {
              .ex1{display: grid; gap: 2rem; font-family: system-ui;}
              .section-title {font-size: 1.5rem;}
              .gridd {padding: 1rem; border: 3px solid dodgerblue;}
              .gridd > * { padding: 1rem; background: orangered;}
            }
          </style>
      

example 3

Regular:

More squished version:

code:
        <div class="ex3">
        <section>
          <h4 class="section-title">Regular:</h4>
          <div class="grida">
            <div></div>
            <div></div>
            <div></div>
          </div>
        </section>
       
        <section class='narrower'>
          <h4 class="section-title">More squished version:</h4>
          <div class="grida">
            <div></div>
            <div></div>
            <div></div>
          </div>
        </section>
      </div>
      <style>
          section {container-type: inline-size;}
          .narrower {width: 80%; margin-inline: auto; }
          .grida {display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, 
            minmax(min(30ch, 100%), 1fr)); container-type: inline-size;}
          .grida > :first-child {/* 2 columns + gap */ @container (width > calc(30ch * 2 + 1rem)) 
            {grid-column: span 2; background: rebeccapurple; } }
          .grida > :first-child { /*  3 columns + gaps */ @container (width > calc(30ch * 3 + 2rem)) 
            {grid-column: span 1; background: olive;}}
          
          @layer not-demo-related {
            .ex3 {display: grid; gap: 2rem; font-family: system-ui;}
            .section-title { font-size: 1.5rem; }
            .grida {padding: 1rem; border: 3px solid dodgerblue;}
            .grida > * {padding: 1rem; background: orangered;}
          }
      </style>
        
      

example 4: with flexbox

Full width parent:

Narrower example:

code:
        <div class="ex4">
          <section>
            <h4 class="section-title">Full width parent:</h4>
            <div class="flex-container">
              <div></div>
              <div></div>
              <div></div>
            </div>
          </section>
          <section class="narrow">
            <h4 class="section-title">Narrower example:</h4>
            <div class="flex-container">
              <div></div>
              <div></div>
              <div></div>
            </div>
          </section>
        </div>
        <style>
          .flex-container {display: flex; gap: 1rem;  flex-wrap: wrap;  container-type: inline-size;}
          .flex-container > * {/* full-width at small sizes */ flex-basis: 100%; flex-grow: 1; 
            /* when there is room for 3 columns including gap */
             @container (width > calc(200px * 3 + 2rem)) { flex-basis: calc(200px);} }
          .narrow { width: 70%; margin-inline: auto; }
          @layer not-demo-related {
            ex4 {display: grid; gap: 2rem; font-family: system-ui; }
            .section-title {font-size: 1.5rem;}
            .flex-container {padding: 1rem; outline: 3px solid dodgerblue;}
            .flex-container > * {  /* padding: 1rem; */ height: 5rem; background: orangered; }
          }
        </style>
      

Browser support

top

As of early 2026, CSS Container Queries are widely supported in modern browsers and considered a stable, production-ready feature for responsive web design.

Chrome: supported since version 105 (September 2022).
Edge: same as Chrome (Chromium-based).
Firefox: supported since version 110 (March 2023).
Safari: supported since version 16 (September 2022)


Good to know...

top

Only ancestors with container-type can be queried.
"container-type: inline-size" is most common (queries width).
Layout containment may affect how children behave (e.g., absolute positioning context).
Avoid over-nesting containers — keep them close to the component root.